package ru.hh.school;

import ru.hh.school.commands.ChatRouter;
import ru.hh.school.exceptions.*;
import ru.hh.school.logging.StaticLogger;
import ru.hh.school.persistence.DataRepository;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;

import static ru.hh.school.util.ResourceManager.message;

public class User {
    private String login;
    private int id;
    private Room room;
    private UserOutput output;
    private Thread outputThread;
    private Thread inputThread;
    private Socket socket;
    private DataRepository dataRepository;
    private boolean alive = true; // This flag protects user from double death. Only single death is allowed.

    public boolean isAlive() {
        return alive;
    }

    public String getLogin() {
        return login;
    }

    public int getId() {
        return id;
    }

    public Room getRoom() {
        return room;
    }

    public void setRoom(Room room) throws InterruptedException {
        this.room = room;
        try {
            dataRepository.setUserRoom(id, room.getId());
        } catch (UserNotFoundException e) {
            StaticLogger.getLogger().logError(e);
        }

    }

    @SuppressWarnings("CaughtExceptionImmediatelyRethrown")
    public User(Socket socket, DataRepository dataRepository, UserManager userManager, RoomManager roomManager, ChatRouter chatRouter)
            throws ChatException {
        this.socket = socket;
        this.dataRepository = dataRepository;
        ObjectInputStream inputStream = null;
        ObjectOutputStream outputStream = null;
        try {
            try {
                inputStream = new ObjectInputStream(socket.getInputStream());
                outputStream = new ObjectOutputStream(socket.getOutputStream());

                String loginString = (String) inputStream.readObject();
                String passwordString = (String) inputStream.readObject();
                User already = userManager.getUserByLogin(loginString);
                if (already != null) {
                    outputStream.writeObject(message("login.already.online"));
                    outputStream.flush();
                    throw new UserAlreadyOnlineException(loginString);
                }

                UserData data = dataRepository.getUserByLoginAndPassword(loginString, passwordString);
                if (data == null) {
                    outputStream.writeObject(message("login.wrong.login.password"));
                    outputStream.flush();
                    throw new UserNotFoundException(loginString, passwordString);
                }

                this.id = data.getId();
                this.login = data.getLogin();
                this.room = roomManager.getRoom(data.getRoomId());
                if (this.room == null) {
                    throw new IncorrectRoomException(data.getRoomId());
                }
                this.room.addUser(this);
                this.room.send(message("user.connected", this.login), this);
                output = new UserOutput(outputStream, this, dataRepository, chatRouter);
                outputThread = new Thread(output);

                UserInput input = new UserInput(inputStream, this, chatRouter);
                inputThread = new Thread(input);
            } catch (IOException e) {
                throw new ClientCommunicationException(e);
            } catch (ClassNotFoundException e) {
                throw new ClientProtocolException(e);
            } catch (InterruptedException e) {
                throw new InnerChatException(e);
            }
        } catch (ChatException exception) {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    StaticLogger.getLogger().logError(e);
                }
            }
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    StaticLogger.getLogger().logError(e);
                }
            }
            try {
                socket.close();
            } catch (IOException e) {
                StaticLogger.getLogger().logError(e);
            }
            throw exception;
        }
    }

    public void start() {
        inputThread.start();
        outputThread.start();
    }

    public void sendMessage(String message) {
        output.add(message);
    }

    public void sendPrivateMessage(PrivateMessage message) {
        output.addSure(message);
    }

    public void close() {
        if (this.alive) {
            inputThread.interrupt();
            outputThread.interrupt();
            room.delUser(this);
            try {
                socket.close();
            } catch (IOException e) {
                //ignore
            }
            this.room = null;
            this.login = null;
            this.alive = false;
        }
    }

    public String toString() {
        return "User[" + this.getLogin() + " in room " + room + "]";
    }
}
